#include "player.hpp"

#include "cue.hpp"

Player::Player()
{
}

Player::~Player()
{
}

static const float RADIUS = 1;
static const float WIDTH = 2 * RADIUS;
static const float STAND_HT = 4;
static const float CROUCH_HT = 3;
static const float WALK_SPEED = 0.125f;
static const float VOLUME = STAND_HT * WIDTH;
static const float INV_VOLUME = 1.f / VOLUME;

const int Player::HPMAX = 1;
static const int AMMO0 = 10;
static const int AMMOMAX = 99;

enum State
{
	STAND,
	CROUCH,
	JUMP,
	USTAIR,
	DSTAIR,
	HURT,
	DEAD,
	SWIM,
	STATECT
};

enum Attack
{
	NOATTACK = -1,
	MELEE,
	SHOT,
	MELEEUP,
	ATTACKCT
};

static Image* attkimgs[STATECT];
static Image* imgs[STATECT];

enum SoundN
{
	STEP,
	SPLASHIN,
	SPLASHOUT,
	OUCH,
	ARGH,
	SHIELDEND,
	SOUNDCT
};

static Sound* snds[SOUNDCT];

bool Player::Vulnerable()
{
	return Live() && state != HURT && shield <= 0;
}

bool Player::Attacking()
{
	return attack != NOATTACK;
}

#include "weapon.hpp"

bool Player::MeleeUpgradable()
{
	return meleetype < Weapon::MELEECT - 1;
}

bool Player::MeleeUpEarned()
{
	return MeleeUpgradable() && ammo >= AMMO0 + 5*meleetype;
}

bool Player::MeleeUpgrading()
{
	return attack == MELEEUP;
}

void Player::Init(float x, float y, Cue & cue)
{
	attack = NOATTACK;
	state = STAND;
	attframe = 0;
	stframe = 0;

	hp = HPMAX;
	ammo = AMMO0;
	shield = 0;
	shieldendwarn = false;
	meleetype = Weapon::MELEE;
	shottype = Weapon::NOTYPE;

	body.size.x = WIDTH;
	body.size.y = STAND_HT;
	body.off.x = -WIDTH * .5f;
	body.off.y = -STAND_HT;
	pos.x = x;
	pos.y = y;
	Face(cue.Dir());
	img = imgs[STAND];
}

void Player::StateChange(int s)
{
	if (s == state)
		return;

	switch (state)
	{
	case CROUCH:
	case JUMP:
		switch (s)
		{
		case STAND:
		case SWIM:
		case HURT:
			body.off.y -= (STAND_HT - CROUCH_HT);
			body.size.y = STAND_HT;
			break;

		default:
			;
		}
		break;

	case STAND:
	case HURT:
	case SWIM:
		switch (s)
		{
		case CROUCH:
		case JUMP:
			body.size.y = CROUCH_HT;
			body.off.y += (STAND_HT - CROUCH_HT);
			break;

		default:
			;
		}
		break;

	default:
		;
	}

	state = s;
	stframe = 0;
}

#include <string>
#include "image.hpp"
#include "sound.hpp"
#include "music.hpp"

bool Player::Open()
{
	const char *DIR = "player/";

	const char *STATESTRS[STATECT] =
	{
		"stand",
		"crouch",
		"jump",
		"ustair",
		"dstair",
		"hurt",
		"dead",
		"ustair"
	};
	
	const char *ATTACKSTRS[STATECT] = 
	{
		"attack",
		"cattack",
		"jattack",
		"usattack",
		"dsattack",
		NULL,
		NULL,
		"attack"
	};

	const char *SOUNDSTRS[SOUNDCT] = 
	{
		"player/step",
		"splashin",
		"splashout",
		"player/ouch",
		"player/argh",
		"player/shieldend"
	};

	int got = AllImgs.BatchGet(imgs, STATECT, DIR, STATESTRS);
	got += AllImgs.BatchGet(attkimgs, STATECT, DIR, ATTACKSTRS);
	got += AllSnds.BatchGet(snds, SOUNDCT, 0, SOUNDSTRS);

	return true;
}

void Player::Close()
{
}

#include "stage.hpp"

#include "input.hpp"

void Player::In(const Vec2f & joy, int buttons)
{
	const float JUMP_VEL0 = -.5625f;
	const float ATTACK_TURNWIN = 1;

	bool abutton = buttons & Inp.CmdBit(Inp.ATTACK);
	bool jbutton = buttons & Inp.CmdBit(Inp.JUMP);

	if (abutton && !Attacking() && state != HURT && Live())
	{
		attframe = 0;
		Face(joy.x);
		if (joy.y < 0
			&& shottype > Weapon::MELEE 
			&& ammo >= Weapon::TCost(shottype))
		{
			attack = SHOT;
		}
		else
		{
			attack = MELEE;
		}
	}
		
	float watervol = Stg.PropArea(Stage::WATER, body, pos);

	if (state == DSTAIR || state == USTAIR)
	{
		Vec2i ustair (StairFind(-1));
		Vec2i dstair (StairFind(1));
		if (dstair.x >= 0)
		{
			int stairtile = Stg.Get(dstair.x, dstair.y);

			if (Stg.PropGet(stairtile, Stg.DRSTAIR))
			{
				if (joy.y)
					vel.y = joy.y;
				else
					vel.y = joy.x;
				vel.x = vel.y;
				vel *= .5f * WALK_SPEED;
				if (attframe < ATTACK_TURNWIN)
				{
					if (joy.x)
						Face(joy.x);
					else
						Face(vel.x);
				}
				if (dir < 0)
					StateChange(USTAIR);
				else 
					StateChange(DSTAIR);
			}
			else if (Stg.PropGet(stairtile, Stg.URSTAIR))
			{
				if (joy.y)
					vel.y = joy.y;
				else
					vel.y = -joy.x;
				vel.x = -vel.y;
				vel *= .5f * WALK_SPEED;
				if (attframe < ATTACK_TURNWIN)
				{
					if (joy.x)
						Face(joy.x);
					else
						Face(vel.x);
				}
				if (dir < 0)
					StateChange(DSTAIR);
				else
					StateChange(USTAIR);
			}
		}
		else
		{
			if (vel.y < 0)
				pos.y = (int)pos.y + 1;
			StateChange(STAND);
		}
	}
	else if (state == STAND)
	{
		vel.x = joy.x * WALK_SPEED;

		if (attframe < ATTACK_TURNWIN)
			Face(joy.x);

		if (jbutton)
		{
			vel.y = JUMP_VEL0;
			StateChange(JUMP);
		}
		else if (joy.y > 0)
		{
			if (StairTake(joy.y).x > -1)
				vel.x = vel.y = 0;
			else	
				StateChange(CROUCH);
		}
		else if (joy.y < 0)
		{
			if (StairTake(joy.y).x > -1)
				vel.x = vel.y = 0;
		}
	}
	else if (state == CROUCH)
	{
		if (attframe < ATTACK_TURNWIN)
			Face(joy.x);
		if (stframe >= 0)
			if (joy.y <= 0)
				StateChange(STAND);
	}
	else if (state == SWIM)
	{
		const float SWIM_ACCEL = 1.f / 64;
		const float SWIM_TERMVEL = SWIM_ACCEL * 8;

		for (int ax = Y; ax > NOAXIS; --ax)
		{
			float targetvel = joy[ax] * SWIM_TERMVEL;
			if (vel[ax] < targetvel)
			{
				vel[ax] = std::min(vel[ax] + SWIM_ACCEL,
						targetvel);
			}
			else
			if (vel[ax] > targetvel)
			{
				vel[ax] = std::max(vel[ax] - SWIM_ACCEL,
						targetvel);
			}
		}

		if (attframe < ATTACK_TURNWIN)
			Face(joy.x);
		
		if (watervol <= .75f * VOLUME)
		{
			if (jbutton /* && pressing against water surface */)
			{
				vel.y = JUMP_VEL0;
				snd = snds[SPLASHOUT];
				StateChange(JUMP);
			}
			else if (joy.y < 0)
			{
				if (StairTake(-1).x > -1)
					vel.x = vel.y = 0;
			}
		}
	}
}

#include "game.hpp"

Vec2i Player::StairFind(int diry)
{
	Vec2i stair (-1, -1);

	Vec2i botcent (pos + body.CentBot());
	if (diry < 0)
		--botcent.y;

	for (int c = botcent.x - 1; stair.x < 0 && c <= botcent.x + 1; ++c)
	{
		int tile = Stg.Get(c, botcent.y);

		if (Stg.PropGet(tile, Stg.URSTAIR)
			|| Stg.PropGet(tile, Stg.DRSTAIR))
		{
			stair = Vec2i(c, botcent.y);
		}
	}

	return stair;
}

Vec2i Player::StairTake(int diry)
{
	Vec2i stair (StairFind(diry));

	if (stair.x < 0)
		return stair;

	int stairtile = Stg.Get(stair.x, stair.y);
	int stairstate = DSTAIR;
	float jigger = .125f;

	if (diry < 0)
	{
		stairstate = USTAIR;
		jigger = 1 - jigger;
	}

	if (Stg.PropGet(stairtile, Stg.DRSTAIR))
	{
		pos.x = stair.x + jigger;
		pos.y = stair.y + jigger;
		Face(diry);
		StateChange(stairstate);
	}
	else if (Stg.PropGet(stairtile, Stg.URSTAIR))
	{
		pos.x = stair.x + 1 - jigger;
		pos.y = stair.y + jigger;
		Face(-diry);
		StateChange(stairstate);
	}

	return stair;
}

void Player::Update() 
{
	// physics/anim update
		
	const float JUMP_ACCEL = 1.f / 32;
	const float JUMP_TERMVEL = 1;

	const float ANIM_FPU = .125f;
	
	float watervol = Stg.PropArea(Stage::WATER, body, pos);

	switch (state)
	{
	case DEAD:
		if (stframe < 1)
			stframe += ANIM_FPU;
		break;

	case JUMP:
	case HURT:
		if (!MeleeUpgrading())
			vel.y = std::min(vel.y + JUMP_ACCEL, JUMP_TERMVEL);
		break;

	case STAND:
		vel.y = 0;
	case USTAIR:
	case DSTAIR:
		if (Attacking())
			vel.x = vel.y = 0;

		if (vel.x || vel.y)
			stframe += ANIM_FPU;
		else
			stframe = 0;
		break;

	case CROUCH:
		vel.x = vel.y = 0;
		if (stframe < 0)
			stframe += ANIM_FPU;
		break;
	
	case SWIM:
		if (!MeleeUpgrading())
			vel.y += JUMP_ACCEL * 2.f
				* (1.f - (watervol * INV_VOLUME));
		break;

	default:
		;
	}

	// move

	for (int ax = Y; ax > NOAXIS && !MeleeUpgrading(); --ax)
	{
		pos[ax] += vel[ax];

		switch (state)
		{
			case USTAIR:
			case DSTAIR:
				break;

			default:
				stghit[ax] = Stg.AxisHit(body, pos, ax);
				if (StgHitting(ax))
					pos[ax] -= stghit[ax];
		}
	}

	// state change

	if (state == USTAIR || state == DSTAIR)
	{
		watervol = Stg.PropArea(Stg.WATER, body, pos);
		if (watervol > VOLUME * .75f)
		{
			StateChange(SWIM);
		}
	}
	else if (state == SWIM)
	{
		if (watervol == 0)
		{
			vel.x = 0;
			StateChange(JUMP);
		}
		else if (StgHitting(Y))
			vel.y = 0;
	}
	else if (state == JUMP || state == HURT)
	{
		if (StgHitting(Y))
		{
			if (0 <= stghit.y)
			{
				if (!Live())
				{
					StateChange(DEAD);
					snd = snds[ARGH];
					vel.x = 0;
					shield = 0;
				}
				else if (vel.y >= JUMP_TERMVEL)
				{
					StateChange(CROUCH);
					stframe = -1;
					snd = snds[STEP];
				}
				else
				{
					StateChange(STAND);
				}
			}
			vel.y = 0;
		}

		watervol = Stg.PropArea(Stg.WATER, body, pos);
		if (watervol >= VOLUME * .75f)
		{
			snd = snds[SPLASHIN];
			StateChange(SWIM);
		}
	}
	else if (state == STAND)
	{
		if (!StgHitting(Y))
		{
			vel.x = 0;
			StateChange(JUMP);
		}
		else if (!Attacking())
		{
			Vec2f bc (pos + body.CentBot());

			if (!Stg.PropGet(Stg.Get(bc.x, bc.y), Stg.SOLID))
			{
				Vec2i stair (-1, -1);

				for (int diry = -1; 
					diry <= 1 && stair.x < 0; 
					diry += 2)
				{
					stair = StairTake(diry);
					if (stair.x > -1)
						vel.x = vel.y = 0;
				}
			}
		}
		
		watervol = Stg.PropArea(Stg.WATER, body, pos);
		if (watervol >= VOLUME * .5f)
		{
			StateChange(SWIM);
		}
	}

	// attack update

	const float ATTACK_WPNFRAME[ATTACKCT] = {0, 2, 0};
	const float ATTACK_ENDFRAME[ATTACKCT] = {3, 3, 1};
	float attkfpus[ATTACKCT] =
	{
		ANIM_FPU,
		ANIM_FPU,
		1.f/64
	};


	if (Attacking())
	{
		if (attframe >= ATTACK_WPNFRAME[attack]
		&& attframe - attkfpus[attack] < ATTACK_WPNFRAME[attack])
		{
			int wtype = meleetype;
			if (attack == SHOT && shottype != Weapon::NOTYPE)
				wtype = shottype;

			Vec2f wpos (WeaponPos());
			Cue wcue(wtype * dir);
			if (!MeleeUpgrading())
			{
				Weapon *w = Wpns.Init(wpos.x, wpos.y, wcue);
				if (w)
				{
					AmmoAdd(-w->Cost());
				}
			}
		}

		attframe += attkfpus[attack];
		if (attframe >= ATTACK_ENDFRAME[attack])
		{
			AttackEnd();
		}
	}

	if (shield > 0)
	{
		const int SHIELDWARNTIME = 60;
		if (shield > SHIELDWARNTIME && shield - 1 <= SHIELDWARNTIME
				&& shieldendwarn)
		{
			snd = snds[SHIELDEND];
			shieldendwarn = false;
		}
		--shield;
	}

	// output update
	
	if (MeleeUpgrading())
	{
		red = !red;
		grn = !grn;
	}
	else
		red = grn = 1;

	img = Attacking() ? attkimgs[state] : imgs[state];
	frame = Attacking() ? attframe : stframe;
	alpha = (float)(shield % 4 < 2);
}


void Player::AttackEnd()
{	
	attframe = 0;
	attack = NOATTACK;
}

#include "enemy.hpp"

void Player::Hit(Enemy & en)
{
	if (Vulnerable() && en.Vulnerable() && en.Damage())
	{
		shield = 120;
		AttackEnd();
		HPAdd(-en.Damage());

		if (!Live() || 
			(state != USTAIR && state != DSTAIR && state != SWIM))
		{
			StateChange(HURT);
			Face(en.pos.x - pos.x);
			vel.x = -WALK_SPEED * dir;
			vel.y = -.375f;
		}

		snd = snds[OUCH];

		if (!Live())
		{
			Kill();
			Mus.Stop();
		}
	}
}

#include "item.hpp"

void Player::Hit(Item & itm)
{
}

#include "door.hpp"

void Player::Hit(Door & door)
{
	if (door.IsShut())
	{
		Vec2f hit = body.HitVec(door.body, door.pos - pos);
		pos.x -= hit.x;
		stghit.x = hit.x;
	}
}

#include "hud.hpp"

void Player::HUDUpdate()
{
	HUD.Put(HUD.GAUGE_HP, hp);
	HUD.Put(HUD.NUM_AMMO, ammo);
	HUD.IconSet(HUD.ICON_SHOT, 
			shottype >= Weapon::MELEECT ? 
			shottype - Weapon::MELEECT 
			: Weapon::NOTYPE);
}

Vec2f Player::WeaponPos()
{
	Vec2f wpos (pos);
	wpos.x += body.AxPoint(X, .5f);
	wpos.y += body.AxPoint(Y, 0) + (STAND_HT * .5f);
	return wpos;
}

void Player::HPAdd(int delta)
{
	hp = std::max(0, std::min(hp + delta, HPMAX));
}

void Player::AmmoAdd(int delta)
{
	ammo = std::max(0, std::min(ammo + delta, AMMOMAX));
}

void Player::MeleeUp()
{
	++meleetype;
	attframe = 0;
	attack = MELEEUP;
}

Player Plr;
